Fix login - #1160
Conversation
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src/AppLayout.tsx" line_range="58-65" />
<code_context>
+ const { data: userDetails, isFetching } = useGetUserByUidQuery(loggedInUser, {
</code_context>
<issue_to_address>
**issue:** Guard against undefined `givenname` when computing `fullName` to avoid rendering `"undefined"`.
Because `userDetails?.givenname !== ""` is true when `givenname` is `undefined`, the header can show `"undefined <sn>"`. Consider a truthiness check or explicitly handling both `undefined` and empty string:
```ts
const fullName = React.useMemo(() => {
if (!userDetails) return "";
if (userDetails.givenname) {
return `${userDetails.givenname} ${userDetails.sn}`;
}
return userDetails.sn;
}, [userDetails]);
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const { data: userDetails, isFetching } = useGetUserByUidQuery(loggedInUser, { | ||
| skip: !loggedInUser, | ||
| }); | ||
|
|
||
| // Retrieve and assign user full name | ||
| const [fullName, setFullName] = React.useState<string>(""); | ||
|
|
||
| React.useEffect(() => { | ||
| if (props.loggedInUser) { | ||
| getUserDetails(props.loggedInUser).then((response) => { | ||
| if ("data" in response) { | ||
| const first = response.data?.result.result.givenname; | ||
| const last = response.data?.result.result.sn; | ||
| // Some users (e.g., admin) don't have first name | ||
| if (!first) { | ||
| setFullName(last as string); | ||
| } else { | ||
| setFullName(first + " " + last); | ||
| } | ||
| } | ||
| }); | ||
| const fullName = React.useMemo(() => { | ||
| if (!userDetails) return ""; | ||
| if (userDetails?.givenname !== "") { |
There was a problem hiding this comment.
issue: Guard against undefined givenname when computing fullName to avoid rendering "undefined".
Because userDetails?.givenname !== "" is true when givenname is undefined, the header can show "undefined <sn>". Consider a truthiness check or explicitly handling both undefined and empty string:
const fullName = React.useMemo(() => {
if (!userDetails) return "";
if (userDetails.givenname) {
return `${userDetails.givenname} ${userDetails.sn}`;
}
return userDetails.sn;
}, [userDetails]);2b2e9a3 to
41ab00a
Compare
carma12
left a comment
There was a problem hiding this comment.
Overall nice solution. Just some details...
| } | ||
|
|
||
| .login-page-list { | ||
| list-style-type: "· "; |
There was a problem hiding this comment.
Not sure if I understand this...
There was a problem hiding this comment.
I've removed the · from each string, instead it's part of the styling of the element, this more follows the common html and css schemantics. The true reason is, that I wanted to insert a link and the PF component only accepts strings...
| isUserLoggedIn: boolean; | ||
| user: string | null; | ||
| error: string | null; | ||
| loggedUser: string | null; |
There was a problem hiding this comment.
This is the same as the user parameter you just deleted. But I understand that the new name is more descriptive... Not sure if it make sense to remove the error parameter, just in case the API call returns an error response (can't recall now the chances of that happening).
There was a problem hiding this comment.
Scrapped, the user now lives only in the global slice, which makes more sense, as this info was duplicated.
| // Store data in global slice (Redux) | ||
| React.useEffect(() => { | ||
| if (!isInitialBatchLoading && initialBatchResponse === undefined) { | ||
| if (initialBatchResponse === undefined) { |
There was a problem hiding this comment.
The old code had if (!isInitialBatchLoading && initialBatchResponse === undefined) which properly guarded against transient undefined states. This PR removed the !isInitialBatchLoading guard, causing a briefly flash the login page during the refetch window. Maybe this change can be be reverted?
There was a problem hiding this comment.
I've scraped this idea, in favor of fully relying on RTK Query, I'm curious about some checks there...
| React.useEffect(() => { | ||
| // We need to refetch data on user change | ||
| if (!isInitialBatchLoading && loggedIn) { | ||
| refetch(); | ||
| } | ||
| }, [loggedIn]); |
There was a problem hiding this comment.
This useEffect uses isInitialBatchLoading and refetch in its body, but only declares [loggedIn] as a dependency.
There was a problem hiding this comment.
I've scraped this idea, all of this is replace by rtk query and immediate caching, instead we refetch whenever we login.
carma12
left a comment
There was a problem hiding this comment.
Overall nice solution. Just some details...
| // Forcing full page to reload and redirect to login page | ||
| window.location.reload(); | ||
| sessionStorage.setItem("isKerberosDisabled", "true"); | ||
| dispatch(setLoggedOut()); |
There was a problem hiding this comment.
I'm thinking that there is no safeguard here (e.g. error shown) in case the logout operation fails, e.g., due to a failed response, network error, server unreachable, 500, etc. Maybe we should consider to add something here? This can be done in a different PR if needed.
There was a problem hiding this comment.
Not sure if this one has been amended? The code seems to be the same...
There was a problem hiding this comment.
I see, true, the principle holds, please see the code:
logout().then((response) => {
if ("data" in response && !response.data?.error) {
sessionStorage.setItem("isKerberosDisabled", "true");
dispatch(logoutUser());
}
});In case of non-ok response (anything else but 2xx), the code throws, therefore .then will not run. We even check for errors in ok responses, not sure what else should be added here.
| // Forcing full page to reload and redirect to login page | ||
| window.location.reload(); | ||
| sessionStorage.setItem("isKerberosDisabled", "true"); | ||
| dispatch(setLoggedOut()); |
There was a problem hiding this comment.
I'm thinking that there is no safeguard here (e.g. error shown) in case the logout operation fails, e.g., due to a failed response, network error, server unreachable, 500, etc. Maybe we should consider to add something here? This can be done in a different PR if needed.
veronnicka
left a comment
There was a problem hiding this comment.
Hi, I managed to reproduce the bug, and then check that this PR really fixes it. As the code goes, Im not sure I fully understand everything but I checked all I could.
In the new PR, I stumbled upon a bug.
The steps:
1.logout from modern webui
2.transfer to old webui with the link
3.logout from old webui
4. transfer to modern webui with the link
5. logout from modern webui -> this logout fails
See the recording below:
Screencast.From.2026-08-17.13-47-50.mp4
| @@ -103,7 +101,7 @@ const LoginMainPage = () => { | |||
|
|
|||
| // Kerberos login when loading the component | |||
| React.useEffect(() => { | |||
| if (!username && isKerberosEnabled) { | |||
| if (!username && !isKerberosDisabled) { | |||
| onKrbLogin().then((response) => { | |||
| if ("error" in response) { | |||
There was a problem hiding this comment.
there is not and else branch to this if, Im not sure if thats a problem but it seems odd.
There was a problem hiding this comment.
This is correct, if isKerberosDisabled then we don't want to perform auto-login, otherwise we would get stuck in the log in loop.
…login after logout Changes: - Create a custom `LoginPage` component that accepts `loginPageContent` as a React node and renders it in the login footer - Update `LoginMainPage` to use the custom `LoginPage` and render login instructions as a `List`, including a link back to the old WebUI - Set an `isKerberosDisabled` flag in `localStorage` on logout to prevent automatic Kerberos re-login on the next visit - Read and clear the flag in `LoginMainPage` to control Kerberos auto-login behavior - Add CSS styling for the login page list bullets Fixes: freeipa#570 Signed-off-by: David Hanina <dhanina@redhat.com>
Changes: - Simplify `auth-slice` to track only `loggedUser` instead of separate `isUserLoggedIn`, `user`, and `error` fields - Rename auth actions to `setLoggedUser` and `setLoggedOut` - Remove local auth state from `App.tsx` and use the Redux `loggedUser` value directly - Use RTK Query's `isFetching` flag for the initial batch loading state - Remove `window.location.reload()` calls after login and logout - Simplify `AppRoutes` by removing the `isInitialDataLoaded` prop and the `DataSpinner` fallback Signed-off-by: David Hanina <dhanina@redhat.com>
Changes: - Replace `loggedUser` string state with `loggedIn` boolean in auth slice - Rename `setLoggedUser` action to `setLoggedIn` - Retrieve logged-in user UID from global slice in `AppLayout` - Use `useGetUserByUidQuery` instead of mutation for user details - Refetch initial batch data when login state changes - Move `isKerberosDisabled` flag from localStorage to sessionStorage - Improve login error handling and validation state updates Signed-off-by: David Hanina <dhanina@redhat.com>
Move AppLayout from a conditional wrapper in App.tsx to a parent route in AppRoutes, rendering nested routes via Outlet. This removes the children prop from AppLayout and simplifies the top-level App rendering. This also fixes a bug where when logged it it incorrectly renders sync-otp or browser-config pages. Signed-off-by: David Hanina <dhanina@redhat.com>
- Add a `userMetadata` query to `rpcAuth` that batches the initial configuration commands (config_show, whoami, env, dns_is_enabled, etc.) and returns a typed `UserMetadata` object. - Simplify `global-slice` to hold `UserMetadata` directly, populating it from the query matcher and clearing `loggedInUser` on rejection. - Remove the dedicated `auth-slice`; derive login state from whether `loggedInUser` is non-empty. - Update `App`, `AppLayout`, `AppRoutes`, `LoginMainPage`, and `ResetPassword` to use the new query and the `loggedInUser` global value. - Refetch `userMetadata` on successful login and logout instead of toggling a boolean auth flag. Assisted-by: Cursor <cursoragent@curosr.com> Signed-off-by: David Hanina <dhanina@redhat.com>
Decouple the two different testing utils as importing from store initializes API, which we want to avoid in some cases. This can be refactored in a nicer way later on. Signed-off-by: David Hanina <dhanina@redhat.com>
Document the authentication state model, application startup/auto-login flow, manual login behavior, logout handling, and Kerberos re-login prevention. Assisted-by: Cursor <cursoragent@cursor.com> Signed-off-by: David Hanina <dhanina@redhat.com>
|
Hi @carma12 , @veronnicka I've added a design doc to help you out comprehend how it works. Admittedly the mermaid slop is generated by AI, but it helps to get the point across and I've verified it thoroughly, the text is my own, and I tried to point out some "caveats" - more of a behaviour that was clearly wrong and fixed that. |
|
@veronnicka as the bug you've mentioned I can't reproduce, from code point of view this also doesn't make sense. Maybe some data went stale during reloads |
| const fullName = React.useMemo(() => { | ||
| if (!userDetails) return ""; | ||
| if (userDetails?.givenname !== "") { | ||
| return userDetails?.givenname + " " + userDetails?.sn; | ||
| } | ||
| }, [props.loggedInUser]); | ||
|
|
||
| return userDetails?.sn; | ||
| }, [userDetails]); |
There was a problem hiding this comment.
The check userDetails?.givenname !== "" is true when givenname is undefined (because undefined !== ""), so the header will show "undefined Smith" for users without a first name (e.g., admin). The old code had a proper truthiness check (if (!first)).
Fix suggestion:
const fullName = React.useMemo(() => {
if (!userDetails) return "";
if (userDetails.givenname) {
return `${userDetails.givenname} ${userDetails.sn}`;
}
return userDetails.sn;
}, [userDetails]);
There was a problem hiding this comment.
Right, I assumed givenname always has to be set, which is true, even administrator has givenname set, to "". Should I still handle this edge case?
| // Forcing full page to reload and redirect to login page | ||
| window.location.reload(); | ||
| sessionStorage.setItem("isKerberosDisabled", "true"); | ||
| dispatch(setLoggedOut()); |
There was a problem hiding this comment.
Not sure if this one has been amended? The code seems to be the same...
This may be tested with the development, but it fixes a bug that exists in production. To test and replicate the fixed bug in production:
Run kinit
Navigate to Modern WebUI (You should get automatically logged in)
Log out -> Stuck in the loop.
The fix allows you to log in as another user on log out, but at the same time if you refresh page, it will still pick up kerberos. The rest is just a simplifications and few other changes, I added a link that takes you back to the old webui, I'd wish I knew who and where was asking for that, but the change seemed minor and made sense. There is also a bunch of simplifications and fixes regarding getting stuck in the login loop.
Summary by Sourcery
Prevent logout from triggering an immediate Kerberos sign-in while retaining automatic session detection and simplifying authentication state management.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests:
Chores: